抢红包软件背后的 Accessibility 服务及启动原理
前言
最近开发的一款设备使用到了Accessibility
功能,Android 提供了Accessibility功能和服务帮助这些用户更加简单地操作设备。
需要实现AccessibilityService
,AccessibilityService是一个系统服务,它运行在后台,并且能够收到由系统发出的一些事件,比如通知状态、view 的一些相关事件,指纹,touch 等。
界面中产生的任何变化都会由系统通知给 AccessibilityService。大家熟知的抢红包
软件,Talkback
都是使用AccessibilityService 实现的。自动化测试
等等,都是基于 Accessibility。
后面几篇文章,将慢慢揭开Accessibility的神秘的面纱。请大家多多关注。
AccessibilityService 是 Service 吗
简单例子这里面就不讲了,大家可以网上搜索下。
AndroidManifest.xml
配置如下
<service
android:name="XXX.Service"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessible_service_config" />//对AccessibilityService 的配置文件
</service>
</application>
AccessibilityService 写法就是 Service
,android:permission
,intent-filter
都是必要配置。
貌似还是 Service 那一套,但又不仅限于此,后面的内容可以发现它是不一般的 Servcie。
AccessibilityService 如何启动的
虽作为 Servcie,App 本身并没有启动和停止它,完全由系统调度,这是第一个不一样的地方。
在 AOSP 中类似的由系统调度的 App Service 有很多,比如 JobService
、AutoFillService
、NotificationAssistantService
等
下面将将具体解答这两个问题:
谁启动和停止 AccessibilityService ? 设备重启了,AccessbilityService 也会启动吗,不会被kill吗?
1. 谁启动了 AccessibilityService
安装AccessibilityService 的应用,会出现在设置-无障碍-应用列表中,选择打开,应用中的AccessibilityService 就启动。
代码上,设置应用 会将AccessibilityService 的 ComponentName
信息 存入 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
AccessibilityManagerService
系统服务将监听该 Map 的变化,启动或者关闭相应 ComponentName 的 AccessibilityService。
流程如下:
监听 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
更新 AccessibilityUserState
遍历 mEnableServices
,如果没有bind 则调用bindService
启动它
2、重启会AccessbilityService 会启动吗,不会被kill?
AccessibilityManagerService 是 SystemService
进程,开机自启动,所以 AccessbilityService 的开机启动就简单了,源码是监听 unlock
广播,
bindService 则使用 Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
FLAG 来启动前台级别的 service,所以不会被Kill。
frameworks/baseservices/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
public void bindLocked() {
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
final long identity = Binder.clearCallingIdentity();
try {
int flags = Context.BIND_AUTO_CREATE
| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE //前台service,所以不会被Kill
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
| Context.BIND_INCLUDE_CAPABILITIES;
if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
if (mService == null && mContext.bindServiceAsUser(
mIntent, this, flags, new UserHandle(userState.mUserId))) {
userState.getBindingServicesLocked().add(mComponentName);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(),
mAccessibilityServiceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
userState.mUserId);
}
注意:开发中遇到过一种情况 AccessibilityService 不会启动,设置 App 中会显示无法运行。点按可查看相关信息的提示,这题由于 AccessibilityService 所在的 App 发生了 crash,而 crash 信息会记录在AccessibilityUserState中,不再自动 bind。
以上流程AccessibilityManagerService 与 应用中AccessibilityService通信,
AccessibilityService 和 AccessibilityManagerService 的关系
分析完以上流程,可以看到
AccessibilityManagerService 充当了 client
AccessibilityService 则是 service
所以调用过程的本质上是AccessibilityManagerService -> AccessibilityService。
可能有朋友会有疑惑:AccessibilityService 虽然本质上是 Service ,但分明又提供了 onServiceConnected 的方法,那它到底属于 client 还是 service?
从以上时序图可以看出,AccessibilityManagerService 绑定 AccessibilityService 成功后,会立即调用 IAccessibilityServiceClient#init()。
init() 的目的很简单:将 AccessibilityManagerService 侧的 IAccessibilityServiceConnection 接口回调回来,可以方便AccessibilityService 反过来调用 AccessibilityManagerService。回调过来的接口正常的话会调用上述提供的 onServiceConnected()。
简言之,这个 onServiceConnected() 指的是 AccessibilityManagerService 的 IAccessibilityServiceConnection 服务准备好了。
public abstract class AccessibilityService extends Service {
...
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
...
public void init(IAccessibilityServiceConnection connection, int connectionId,
IBinder windowToken) {
// Binder 线程切换到 Main 线程
// 传递 id 和 AccessibilityManagerService 返回的 connection 接口
Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
connection, windowToken);
mCaller.sendMessage(message);
}
...
@Override
public void executeMessage(Message message) {
switch (message.what) {
...
case DO_INIT: {
mConnectionId = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
IAccessibilityServiceConnection connection =
(IAccessibilityServiceConnection) args.arg1;
IBinder windowToken = (IBinder) args.arg2;
args.recycle();
// 如果系统返回的 connection 没有问题
// 回调 AccessibilityService 自己的初始化
// 并回调提供的 onServiceConnected()
if (connection != null) {
AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
connection);
mCallback.init(mConnectionId, windowToken);
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance().removeConnection(
mConnectionId);
mConnectionId = AccessibilityInteractionClient.NO_ID;
AccessibilityInteractionClient.getInstance().clearCache();
mCallback.init(AccessibilityInteractionClient.NO_ID, null);
}
return;
...
}
}
}
}
}
所以说:AccessibilityService 这种特殊的 Service,既是供 AccessibilityManagerService 传递无障碍事件的 service
,同时又是会反向调用 AccessibilityManagerService 的 client
。
那 AccessibilityService 什么时候需要反向调用 AccessibilityManagerService 呢?
其实这种的场景很多,这构成了 AccessibilityService 功能的重要部分,包括:
动态更新 Accessibility 的配置(serServiceInfo()) 发出具体的手势(dispatchGesture()) 发出截图的请求(takeScreenshot()) 发出屏幕缩放的请求(setMagnificationScaleAndCenter()) 等等
下面提及的 Accessibility 配置的动态更新正是这个场景之一!
Accessibility 配置的加载
AccessibilityService 支持很多配置,但是很多配置在实际开发中都是用不到的。配置的方式有静态
和动态
两种。
静态配置(更为推荐) 就像文章开头在
meta-data
里配置信息动态配置 运行中调用 serServiceInfo() 根据需要动态更新配置
从上图可以看出,
静态配置:AccessibilityManagerService 通过
PackageManager
获取xml
的配置信息,就转化为AccessibilityServiceInfo
,AccessibilityServiceInfo 保存在System Server
进程动态配置:app 使用
IAccessbilityServiceConnection
接口作为桥梁,去调用 System Server ,设置 AccessibilityServiceInfo
具体配置的内容,这里就不多讲了,整个Accessibility 的内容是很庞大的,所以相关配置也比较多。后面文章会结合功能跟大家分享,这样也更容易懂,这里就跟大家卖个关子吧。
AccessibilityService 的调试
上面说过开启的 AccessibilityService 会存在 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
中,通过相应的 adb
命令可以实时打印这些信息,高效调试。
adb shell settings get secure enabled_accessibility_services
可以读当然也可以写,免去了到 Settings
App 里手动打开:
adb shell settings put secure enabled_accessibility_services <com.example.xxx/.xxxService>
注意:参数是所有打开的 Service 列表,新写入的 Service 名称要在已有的 Service 后面追加,用:
隔开。不然会清除其他 开启的Servcie。
adb shell settings put secure enabled_accessibility_services <xxx/.xxxService:xxx/.xxxService:xxx/.xxxService>
除了查看开启,同时还有个命令可以观察 AccessibilityService 的 bound
、unbound
和 crash
等详细信息。
adb shell dumpsys accessibility
总结
本次主要分享了AccessibilityService 是如何启动, AccessibilityService 如何与SystemServer 中 AccessibilityManagerService 如何相互调用。
大概关系如图所示:
AccessibityManagerService 创建和管理 AccessibilityServiceConnection
AccessibilityServiceConnection 与 App 中的 AccessibilityService
一 一对应AccessibilityService 将 IAccessibilityServiceClient
接口暴露给 AccessibityManagerService 调度AccessibityManagerService 将 IAccessibilityServiceConnection
接口传递给 AccessibilityService 回调
再次梳理下 AccessibilityService 的特点:
由 AccessibityManagerService
bind 和 unbind设备重启会 自行启动
拥有 前台 Service
的 Flag,优先级高不会被 kill 掉接受 AccessibityManagerService 的调度,同时会反向调用,既是 service
又是client
推荐阅读: